package cn.virens.server.tio_tcp;

import java.nio.ByteBuffer;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.server.intf.ServerAioHandler;

import cn.virens.common.BytesBuffer;
import cn.virens.common.BytesBuilder;
import cn.virens.common.utils.number.ByteUtil;
import cn.virens.server.tio_tcp.handler.PacketHandler;
import cn.virens.server.tio_tcp.packet.TioErrorPacket;
import cn.virens.server.tio_tcp.packet.TioReadPacket;
import cn.virens.server.tio_tcp.packet.TioWritePacket;

public class TIOServerHandler implements ServerAioHandler {
	private static Logger logger = LoggerFactory.getLogger(TIOServerHandler.class);
	private static final byte PACKET_FOOTR = 0x3e;
	private static final byte PACKET_HEAD = 0x3c;

	private final List<PacketHandler> handlerMap;

	public TIOServerHandler(List<PacketHandler> handlerMap) {
		this.handlerMap = handlerMap;
	}

	@Override
	public Packet decode(ByteBuffer byteBuffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
		logger.debug("接收到数据({})->{}", TIOServerUtil.name(channelContext), ByteUtil.toHex(bs(byteBuffer, position, readableLength)));

		BytesBuffer buffer = BytesBuffer.of(byteBuffer);
		while (buffer.mark() >= 8) {
			if (byteBuffer.get() != PACKET_HEAD) continue;

			// 构建数据包
			TioReadPacket packet = new TioReadPacket();
			packet.setLength(buffer.getShort());
			packet.setCommand(buffer.get());
			packet.setSeq(buffer.get());

			// 验证 长度是否足够
			if (packet.getLength() + 3 <= buffer.remaining()) {
				// 判断帧尾是否制定数据，如果不是说明帧头无效需跳过
				if (buffer.get(packet.getLength() + 2) == PACKET_FOOTR) {
					byte[] bytes = new byte[packet.getLength()];
					for (int i = 0; i < bytes.length; i++) {
						bytes[i] = buffer.get();
					}

					// 读取 & 计算CRC值
					int crc0 = toCRC(packet, bytes) & 0xffff;
					int crc1 = buffer.getShort() & 0xffff;

					// 做CRC校验，并重读帧尾（主要作用为偏移掉帧尾字节）
					if (crc0 == crc1 && buffer.get() == PACKET_FOOTR) {
						return packet.warp(bytes);
					} else {
						return error(bytes);
					}
				} else {
					buffer.reset(1);
				}
			} else {
				buffer.reset(0);
			}
		}

		return null;
	}

	/**
	 * 3c len cmd seq data crc 3e
	 */
	@Override
	public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
		if (packet instanceof TioWritePacket) {
			TioWritePacket pkt = (TioWritePacket) packet;

			// 构建数据内容
			BytesBuilder body = new BytesBuilder();
			body.append(pkt.lenByte());
			body.append(pkt.cmdByte());
			body.append(pkt.seqByte());
			body.append(pkt.dataByte());

			// 构建完整包
			BytesBuilder data = new BytesBuilder();
			data.append(PACKET_HEAD, 1);
			data.append(body.toArray());
			data.append(toCRC(body), 2);
			data.append(PACKET_FOOTR, 1);

			logger.debug("已发送数据包:" + data.toString());

			return ByteBuffer.wrap(data.toArray());
		} else if (packet != null) {
			logger.error("待发送包不支持:" + packet.getClass().getName());
		} else {
			logger.error("待发送包为空 ");
		}

		return null;
	}

	@Override
	public void handler(Packet packet, ChannelContext channelContext) throws Exception {
		try {
			if (packet instanceof TioReadPacket) {
				for (PacketHandler handler : handlerMap) {
					if (handler.can(((TioReadPacket) packet).getCommand())) {
						handler.read((TioReadPacket) packet, channelContext);
					}
				}
			} else if (packet instanceof TioErrorPacket) {
				logger.error("错误包:" + String.valueOf(packet));
			} else {
				logger.error("无效包:" + String.valueOf(packet));
			}
		} catch (Exception e) {
			logger.error("包处理异常:" + e.getMessage(), e);
		} finally {
			//CloseableUtil.close(App.db());
		}
	}

	private static TioErrorPacket error(byte[] bs) {
		return new TioErrorPacket(bs, "CRC校验失败");
	}

	private static byte[] bs(ByteBuffer buffer, int position, int readableLength) {
		byte[] answer = new byte[readableLength];
		for (int i = 0; i < answer.length; i++) {
			answer[i] = buffer.get(position + i);
		}

		return answer;
	}

	/** CRC校验值 */
	private static int toCRC(TioReadPacket packet, byte[] bs) {
		BytesBuilder builder = new BytesBuilder();
		builder.append(packet.getLength(), 2);
		builder.append(packet.getCommand(), 1);
		builder.append(packet.getSeq(), 1);
		builder.append(bs);

		return toCRC(builder);
	}

	/** CRC校验值 */
	private static int toCRC(BytesBuilder builder) {
		return ByteUtil.toCRC(0x0000, 0x8408, builder.toArray());
	}

}